﻿using Quartz;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Hinacom.Scheduler
{
    /// <summary>
    /// 提供方法将JobInfo对象转换为Quartz IJobDetail和ITrigger对象，或反向转换。
    /// </summary>
    public abstract class JobInfoConverter
    {
        private static Dictionary<Type, JobInfoConverter> converterCache = new Dictionary<Type, JobInfoConverter>();
        /// <summary>
        /// 将JobInfo对象转换为Quartz IJobDetail和ITrigger对象。
        /// </summary>
        /// <param name="jobInfo">源JobInfo对象。</param>
        /// <param name="jobDetail">转换后的IJobDetail对象。</param>
        /// <param name="trigger">转换后的ITrigger对象。</param>
        public static void ToQuartzJobDetailAndTrigger(JobInfo jobInfo, out IJobDetail jobDetail, out ITrigger trigger)
        {
            Type type = jobInfo.GetType();
            // 根据JobInfoConverterAttribute来获取JobInfoConverter实现类的实例
            if (!converterCache.ContainsKey(type))
            {
                var attribute = (JobInfoConverterAttribute)Attribute.GetCustomAttribute(type, typeof(JobInfoConverterAttribute));
                if (attribute == null)
                    throw new JobInfoConverterAttributeMissingException(type);

                converterCache.Add(type, (JobInfoConverter)attribute.Type.GetConstructor(new Type[0]).Invoke(new object[0]));
            }

            JobInfoConverter converter = converterCache[type];

            jobDetail = converter.CreateJobDetail(jobInfo);
            trigger = converter.CreateTrigger(jobInfo);
        }
        /// <summary>
        /// 将Quartz IJobDetail和ITrigger对象转换为JobInfo对象。
        /// </summary>
        /// <param name="jobDetail">源IJobDetail对象。</param>
        /// <param name="trigger">源ITrigger对象。</param>
        /// <param name="jobInfo">转换后的JobInfo对象。</param>
        public static void ToJobInfo(IJobDetail jobDetail, ITrigger trigger, out JobInfo jobInfo)
        {
            JobInfoConverter converter = null;
            if (trigger is ISimpleTrigger)
                converter = new SimpleJobInfoConverter();
            if (trigger is IDailyTimeIntervalTrigger)
                converter = new DailyJobInfoConverter();
            if (converter == null)
                throw new Exception("Converting to JobInfo object failed. The current trigger and schedule type are not supported for now.");
            jobInfo = converter.CreateJobInfo(jobDetail, trigger);
        }
        /// <summary>
        /// 将JobInfo转换成IJobDetail对象时，根据源JobInfo对象创建JobBuilder并初始化通用属性。
        /// </summary>
        protected JobBuilder CreateJobBuilderBase(JobInfo jobInfo)
        {
            Type jobType = SchedulerJobFactory.GetJobType(jobInfo.JobTypeName);

            JobBuilder builder = JobBuilder.Create(jobType);
            builder.WithIdentity(JobKey.Create(jobInfo.Name, jobInfo.Group));
            if (!String.IsNullOrWhiteSpace(jobInfo.Description))
                builder.WithDescription(jobInfo.Description);

            JobDataMap dataMap = new JobDataMap(jobInfo.Parameter);
            builder.SetJobData(dataMap);

            return builder;
        }
        /// <summary>
        /// 将JobInfo转换成ITrigger对象时，根据源JobInfo对象创建TriggerBuilder并初始化通用属性。
        /// </summary>
        protected TriggerBuilder CreateTriggerBuilderBase(JobInfo jobInfo)
        {
            TriggerBuilder builder = TriggerBuilder.Create();
            if (jobInfo.StartTimeUtc.HasValue)
                builder.StartAt(jobInfo.StartTimeUtc.Value);
            else
                builder.StartNow();

            if (jobInfo.EndTimeUtc.HasValue)
                builder.EndAt(jobInfo.EndTimeUtc.Value);

            return builder;
        }
        /// <summary>
        /// 将IJobDetail和ITrigger对象转换为JobInfo对象时，初始化JobInfo对象的通用属性。
        /// </summary>
        protected void CreateJobInfoBase(IJobDetail jobDetail, ITrigger trigger, JobInfo jobInfo)
        {
            jobInfo.Name = jobDetail.Key.Name;
            jobInfo.Group = jobDetail.Key.Group;
            jobInfo.Description = jobDetail.Description;

            jobInfo.JobTypeName = SchedulerJobFactory.GetConfiguredJobTypeName(jobDetail.JobType);

            jobInfo.StartTimeUtc = new DateTime(trigger.StartTimeUtc.Ticks, DateTimeKind.Utc);
            if (trigger.EndTimeUtc.HasValue)
                jobInfo.EndTimeUtc = new DateTime(trigger.EndTimeUtc.Value.Ticks, DateTimeKind.Utc);
            // Data
            foreach(string key in jobDetail.JobDataMap.Keys)
            {
                if (jobInfo.Parameter.ContainsKey(key))
                    jobInfo.Parameter[key] = jobDetail.JobDataMap[key];
                else
                    jobInfo.Parameter.Add(key, jobDetail.JobDataMap[key]);
            }
        }
        /// <summary>
        /// 由子类实现时，根据JobInfo对象创建IJobDetail对象。
        /// </summary>
        /// <param name="jobInfo">源JobInfo对象。</param>
        /// <returns>创建的IJobDetail对象。</returns>
        protected abstract IJobDetail CreateJobDetail(JobInfo jobInfo);
        /// <summary>
        /// 由子类实现时，根据JobInfo对象创建ITrigger对象。
        /// </summary>
        /// <param name="jobInfo">源JobInfo对象。</param>
        /// <returns>创建的ITrigger对象。</returns>
        protected abstract ITrigger CreateTrigger(JobInfo jobInfo);
        /// <summary>
        /// 由子类实现时，根据IJobDetail和ITrigger对象创建JobInfo对象。
        /// </summary>
        /// <param name="jobDetail">源IJobDetail对象。</param>
        /// <param name="trigger">源ITrigger对象。</param>
        /// <returns>创建的JobInfo对象。</returns>
        protected abstract JobInfo CreateJobInfo(IJobDetail jobDetail, ITrigger trigger);
    }
    /// <summary>
    /// 转换SimpleJobInfo对象。
    /// </summary>
    public class SimpleJobInfoConverter : JobInfoConverter
    {
        protected override IJobDetail CreateJobDetail(JobInfo jobInfo)
        {
            JobBuilder builder = CreateJobBuilderBase(jobInfo);
            return builder.Build();
        }
        protected override ITrigger CreateTrigger(JobInfo jobInfo)
        {
            SimpleJobInfo simpJob = (SimpleJobInfo)jobInfo;

            TriggerBuilder builder = CreateTriggerBuilderBase(jobInfo);

            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.Create();

            scheduleBuilder.WithInterval(simpJob.Interval);

            if (simpJob.RepeatCount >= 0)
                scheduleBuilder.WithRepeatCount(simpJob.RepeatCount);
            else
                scheduleBuilder.RepeatForever();

            builder.WithSchedule(scheduleBuilder);

            return builder.Build();
        }
        protected override JobInfo CreateJobInfo(IJobDetail jobDetail, ITrigger trigger)
        {
            var jobInfo = new SimpleJobInfo();
            CreateJobInfoBase(jobDetail, trigger, jobInfo);

            ISimpleTrigger simpTrigger = (ISimpleTrigger)trigger;
            jobInfo.Interval = simpTrigger.RepeatInterval;
            jobInfo.RepeatCount = simpTrigger.RepeatCount;

            return jobInfo;
        }
    }
    public class DailyJobInfoConverter : JobInfoConverter
    {
        protected override IJobDetail CreateJobDetail(JobInfo jobInfo)
        {
            JobBuilder builder = CreateJobBuilderBase(jobInfo);
            return builder.Build();
        }

        protected override JobInfo CreateJobInfo(IJobDetail jobDetail, ITrigger trigger)
        {
            var jobInfo = new DailyJobInfo();
            CreateJobInfoBase(jobDetail, trigger, jobInfo);

            IDailyTimeIntervalTrigger dailyTrigger = (IDailyTimeIntervalTrigger)trigger;
            jobInfo.StartDailyAt = new TimeSpan(dailyTrigger.StartTimeOfDay.Hour, dailyTrigger.StartTimeOfDay.Minute, dailyTrigger.StartTimeOfDay.Second);
            jobInfo.EndDailyAt = new TimeSpan(dailyTrigger.EndTimeOfDay.Hour, dailyTrigger.EndTimeOfDay.Minute, dailyTrigger.EndTimeOfDay.Second);
            jobInfo.RunOnWeekDays = dailyTrigger.DaysOfWeek.ToArray();
            long interval = dailyTrigger.RepeatInterval;
            switch (dailyTrigger.RepeatIntervalUnit)
            {
                case IntervalUnit.Second:
                    interval *= 1000;
                    break;
                case IntervalUnit.Minute:
                    interval *= 60 * 1000;
                    break;
                case IntervalUnit.Hour:
                    interval *= 3600 * 1000;
                    break;
            }
            jobInfo.Interval = new TimeSpan(interval * 10000);
            
            return jobInfo;
        }

        protected override ITrigger CreateTrigger(JobInfo jobInfo)
        {
            DailyJobInfo dailyJob = (DailyJobInfo)jobInfo;

            TriggerBuilder builder = CreateTriggerBuilderBase(jobInfo);

            DailyTimeIntervalScheduleBuilder scheduleBuilder = DailyTimeIntervalScheduleBuilder.Create();
            scheduleBuilder.OnDaysOfTheWeek(dailyJob.RunOnWeekDays);

            scheduleBuilder.StartingDailyAt(new TimeOfDay(dailyJob.StartDailyAt.Hours, dailyJob.StartDailyAt.Minutes, dailyJob.StartDailyAt.Seconds));
            scheduleBuilder.WithInterval(Convert.ToInt32(dailyJob.Interval.TotalSeconds), IntervalUnit.Second);

            if (dailyJob.EndDailyAt.HasValue)
                scheduleBuilder.EndingDailyAt(new TimeOfDay(dailyJob.EndDailyAt.Value.Hours, dailyJob.EndDailyAt.Value.Minutes, dailyJob.EndDailyAt.Value.Seconds));
            if (dailyJob.EndDailyAfterCount.HasValue)
                scheduleBuilder.EndingDailyAfterCount(dailyJob.EndDailyAfterCount.Value);

            builder.WithSchedule(scheduleBuilder);

            return builder.Build();
        }
    }
    public class CalendarJobInfoConverter : JobInfoConverter
    {
        protected override IJobDetail CreateJobDetail(JobInfo jobInfo)
        {
            throw new NotImplementedException();
        }

        protected override JobInfo CreateJobInfo(IJobDetail jobDetail, ITrigger trigger)
        {
            throw new NotImplementedException();
        }

        protected override ITrigger CreateTrigger(JobInfo jobInfo)
        {
            throw new NotImplementedException();
        }
    }
}
